<?php

/**
 * This plugin allows the detection of emulated HTTP PUT/DELETE requests in POST 
 * requests. As browsers do not reliably implement HTTP PUT/DELETE, all forms 
 * POSTing to either an edit or destroy action should use PUT and DELETE, respectively.
 * These methods should be specified by a hidden input element in the associated form 
 * of the following format:
 * 
 * <input name="_method" type="hidden" value="put|delete"/>
 * 
 * This plugin attaches to the routeShutdown hook, which runs immediately after
 * the router finishes routing the request.
 * 
 * Requests will be routed as follows:
 * 
 * GET  ':controller'           => :controller/index
 * GET  ':controller/:id'       => :controller/show/:id
 * GET  ':controller/new'       => :controller/new
 * GET  ':controller/:id/edit   => :controller/edit/:id
 * POST ':controller'           => :controller/create
 * PUT  ':controller/:id'       => :controller/update/:id
 * DELETE ':controller/:id'     => :controller/destroy/:id
 *
 * @package default
 * @author David Abdemoulaie
 **/
class Rest_Controller_Plugin_Routes extends Zend_Controller_Plugin_Abstract
{
    /**
    * Fix for those using addDefaultRoutes, or with their own
    * custom routes that would possibly conflict.
    * The routes already specified are simply inserted
    * in between these.
    * 
    * @var array
    **/
    protected $_routes = array('generic'    =>  array(),
                               'specific'   =>  array());
    /**
    * Pulls in routes from a config file and assigns routes
    * to the router registered with Zend_Controller_Front.
    *
    * @todo travel down the tree and make multiple routes for 
    * all parents with generate="both"
    * @param Zend_Controller_Request_Abstract $request
    * @return void
    * @author Jason Eisenmenger <jasoneisen at gmail>
    **/
    public function routeStartup(Zend_Controller_Request_Abstract $request)
    {
        if (!Zend_Loader::isReadable(PATH_APP . 'configs/routes.xml')) {
            return;
        }
        
        $routes = simplexml_load_file(PATH_APP . 'configs/routes.xml');
        
        $front = Zend_Controller_Front::getInstance();
        $router = $front->getRouter();
        $appRoutes = $router->getRoutes();
        
        foreach ($appRoutes as $name => $route) {
            $router->removeRoute($name);
        }
        
        foreach ($routes as $name => $route) {
            
            if (isset($route['extends'])) {
                
                foreach (explode("|", $route['extends']) as $extension) {
                
                    $prefix = $this->_generatePrefixes($route, $routes, $extension);
                    $this->_generateRoutes($name, $route, $prefix);
                }
            }

            if (!isset($route['extends']) || (isset($route['generate']) && $route['generate'] == 'both')) {
                
                $this->_generateRoutes($name, $route);
            }
        }
        
        $router->addRoutes($appRoutes);
        $router->addRoutes($this->_routes['generic']);
        $router->addRoutes($this->_routes['specific']);
    }
    
    /**
    * Traverses the route tree building prefixes for the given route
    * 
    * @param object $route
    * @param object $routes
    * @return array An array of prefixes
    * @author Jason Eisenmenger <jasoneisen at gmail>
    **/
    protected function _generatePrefixes($route, $routes, $extension = null)
    {
        $prefix = array('name'=>'', 'path'=>'');
        
        // Trickery
        if (null !== $extension) {
            $route['extends'] = $extension;
        }
        
        while(isset($route['extends'])) {
            
            // Switch up
            $singular = $route['extends'];
            $route = $routes->$singular;
            
            $plural = isset($route->plural) ? $route->plural : $singular . 's';
            
            //$path = isset($route->controller) ? $route->controller : $singular;
            
            $prefix['path'] = $singular.'/:'.$singular.'_id/'.$prefix['path'];
            $prefix['name'] = $singular .'_'. $prefix['name'];
            $prefix['regex'][$singular.'_id'] = isset($route->regex) ? (string)$route->regex : '\d+';
            
            if (isset($route['generate']) && $route['generate'] == 'both') {
                // @todo maybe this can be where routes are split for children?
            }
        }
        
        if (substr($prefix['name'], 0, 1) == '_') {
            $prefix['name'] = substr($prefix['name'], 1, strlen($prefix['name'])-1) . '_';
        }
        
        return $prefix;
    }
    
    /**
    * Generates routes and sends them to $this->_routes
    * 
    * @param string $name
    * @param object $route
    * @param array $prefix
    * @return void
    * @author Jason Eisenmenger <jasoneisen at gmail>
    **/
    protected function _generateRoutes($name, $route, $prefix = array('name'=>'', 'path'=>'', 'regex'=>array()))
    {
        $front = Zend_Controller_Front::getInstance();

        $singular = isset($route->singular) ? $route->singular : $name;
        $plural = isset($route->plural) ? $route->plural : $singular . 's';
        
        $controller = isset($route->controller) ? $route->controller : $plural;
        $plural_path = ($singular == $plural) ? $singular . 's' : $plural;
        
        $prefix['path'] = ($prefix['path'] === null) ? $plural : $prefix['path'] . $plural;
        
        if (isset($route->module)) {
            
            $module = (string) $route->module;
            
            if (!isset($route->module['display']) || $route->module['display'] == 'true') {
                
                $prefix['path'] = $module.'/'. $prefix['path'];
            }
            
            $prefix['name'] = $module .'_'. $prefix['name'];
        } else {
            $module = $front->getDefaultModule();
        }
        
        if (!empty($prefix['name'])) {
            $type = 'specific';
        } else {
            $type = 'generic';
        }
        
        $regex = isset($route->regex) ? (string) $route->regex : '\d+';
        $singularRegex = array_merge(array('id'=>$regex), $prefix['regex']);
        
        if (!isset($route['rest']) || $route['rest'] == 'true') {
            
            // plural_path    =>    /teams
            $this->_routes[$type][$prefix['name'].$plural_path.'_path'] = new Zend_Controller_Router_Route(
                $prefix['path'].'/:action/*', array('module'     =>  $module,
                                            'controller' =>  $controller,
                                            'action'     =>  $front->getDefaultAction()), $prefix['regex']
            );
            
            // new_singular_path     =>    /teams/new
            $this->_routes['specific']['new_'.$prefix['name'].$singular.'_path'] = new Zend_Controller_Router_Route(
                $prefix['path'].'/new/*', array('module'     =>  $module,
                                                'controller' =>  $controller,
                                                'action'     =>  'new'), $prefix['regex']
            );
            
            // singular_path     =>    /teams/1
            $this->_routes['specific'][$prefix['name'].$singular.'_path'] = new Zend_Controller_Router_Route(
                $prefix['path'].'/:id/*', array('module'     =>  $module,
                                                'controller' =>  $controller,
                                                'action'     =>  'show'),    $singularRegex
            );
            
            // edit_singular_path     =>    /teams/1/edit
            $this->_routes['specific']['edit_'.$prefix['name'].$singular.'_path'] = new Zend_Controller_Router_Route(
                $prefix['path'].'/:id/edit/*', array('module'        =>  $module,
                                                     'controller'    =>  $controller,
                                                     'action'        =>  'edit'),    $singularRegex
            );
            
            // delete_singular_path     =>    /teams/1/delete
            $this->_routes['specific']['delete_'.$prefix['name'].$singular.'_path'] = new Zend_Controller_Router_Route(
                $prefix['path'].'/:id/delete/*', array('module'        =>  $module,
                                                       'controller'    =>  $controller,
                                                       'action'        =>  'delete'),    $singularRegex
            );
            
        }
        
        // Custom defined actions
        if (isset($route->custom)) {
            
            foreach ($route->custom->children() as $customName => $customRoute) {
                
                $customPrefix = ($front->getDefaultAction() == $customName) ? '' : $customName .'_';
                
                if (isset($route['rest']) && $route['rest'] == 'false') {
                    
                    $this->_routes['specific'][$customPrefix.$prefix['name'].$singular.'_path'] = new Zend_Controller_Router_Route(
                        $prefix['path'].'/'.$customName .'/*', array('module'        =>  $module,
                                                               'controller'    =>  $controller,
                                                               'action'        =>  $customName),    $singularRegex
                    );
                    
                } elseif (!isset($customRoute['tense']) || $customRoute['tense'] == 'plural') {
                
                    $this->_routes['specific'][$customPrefix.$prefix['name'].$plural_path.'_path'] = new Zend_Controller_Router_Route(
                        $prefix['path'].'/'.$customName.'/*', array('module'     =>  $module,
                                                    'controller' =>  $controller,
                                                    'action'     =>  $customName), $prefix['regex']
                    );
                } else {
                    
                    $this->_routes['specific'][$customPrefix.$prefix['name'].$singular.'_path'] = new Zend_Controller_Router_Route(
                        $prefix['path'].'/:id/'.$customName .'/*', array('module'        =>  $module,
                                                               'controller'    =>  $controller,
                                                               'action'        =>  $customName),    $singularRegex
                    );
                }
            }
        }
    }
    
    /**
     * undocumented function
     *
     * @param Zend_Controller_Request_Abstract $request
     * @return void
     * @author David Abdemoulaie
     **/
    public function routeShutdown(Zend_Controller_Request_Abstract $request)
    {
        if ($request->isPost()) {
            switch ($request->getParam('_method')) {
                case 'post':
                    $request->setActionName('create');
                    break;
                    
                case 'put':
                    $request->setActionName('update');
                    break;
                    
                case 'delete':
                    $request->setActionName('destroy');
                    break;
                    
                default:    
                    break;
            }
        } 
    }
}